import {
  AfterContentInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ContentChildren,
  Input,
  QueryList,
  ViewEncapsulation,
} from '@angular/core';
import { animate, state, style, transition, trigger, group } from '@angular/animations';
import { Subscription } from 'rxjs';
import { doFnResult, TypeUtils } from '../core/utils/api';
import { LvSizeLD } from '../core/types';
import { MotionDuration, MotionFunc } from '../core/animation/api';
import { LvConfig } from '../core/config';
import { CollapsePanelComponent } from './collapse-panel.component';

@Component({
  selector: 'lv-collapse, [lv-collapse]',
  exportAs: 'lvCollapse',
  templateUrl: './collapse.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush,
  encapsulation: ViewEncapsulation.None,
  host: {
    '[class.lv-collapse]': 'true',
    '[class.lv-collapse-panel-simple]': 'lvType === "simple"',
    '[class.lv-collapse-panel-large]': 'lvSize === "large"',
  },
  animations: [
    trigger('showPanel', [
      state('hide', style({ opacity: 0, height: 0 })),
      state('show', style({ opacity: 1, height: '*' })),
      transition('* => show', [
        group([
          style({ opacity: 0, height: 0 }),
          animate(`${MotionDuration.MD} ${MotionFunc.EASE_IN_OUT}`, style({ opacity: 1 })),
          animate(`${MotionDuration.MD} ${MotionFunc.EASE}`, style({ height: '*' })),
        ]),
      ]),
      transition(
        'show => hide',
        group([
          animate(`${MotionDuration.SM} ${MotionFunc.EASE_IN_OUT}`, style({ opacity: 0 })),
          animate(`${MotionDuration.SM} ${MotionFunc.EASE}`, style({ height: 0 })),
        ]),
      ),
    ]),
  ],
})
export class CollapseComponent implements AfterContentInit {
  @Input() lvSize: LvSizeLD = 'default';

  @Input() lvMultiExpansion: boolean = true;

  @Input() lvType: 'default' | 'simple' = 'default';

  @ContentChildren(CollapsePanelComponent) collapsePanels: QueryList<CollapsePanelComponent>;

  @Input() lvPanelClass: string = '';

  @Input() lvAfterExpanded: (collapse) => any;

  @Input() lvBeforeExpanded: (collapse) => any;

  @Input() lvAfterCollapsed: (collapse) => any;

  @Input() lvBeforeCollapsed: (collapse) => any;

  public typeUtils = TypeUtils;

  public assetsPrefixPath = LvConfig.assetsPrefixPath;
  /**
   * 监听映射
   */
  subScribeMap: WeakMap<CollapsePanelComponent, { expand$: Subscription; viewChange$: Subscription }> = new WeakMap();

  constructor(private cdr: ChangeDetectorRef) {}

  collapseClick(index: number, collapsePanel: CollapsePanelComponent): void {
    if (collapsePanel.lvDisabled || collapsePanel.lvLoading) {
      return;
    }
    collapsePanel.expand$.next(!collapsePanel.lvExpanded);
  }

  hidePanel(collapsePanel: CollapsePanelComponent): void {
    const fn = (): void => {
      collapsePanel.animationState = 'hide';
      const subscribe = collapsePanel.openState$.subscribe((open): void => {
        if (!open) {
          if (this.lvAfterCollapsed) {
            this.lvAfterCollapsed(collapsePanel);
          }
          collapsePanel.collapsePanelShow = false;
          collapsePanel.lvExpanded = false;
          subscribe.unsubscribe();
        }
      });
    };

    if (!this.lvBeforeCollapsed) {
      fn();
    } else {
      const call = this.lvBeforeCollapsed(collapsePanel);
      doFnResult(call, (result): void => {
        if (result) {
          fn();
        }
      });
    }
  }

  showPanel(index: number, collapsePanel: CollapsePanelComponent): void {
    if (!this.lvMultiExpansion) {
      this.collapsePanels.forEach((cp): void => {
        this.hidePanel(cp);
      });
    }

    const fn = (): void => {
      collapsePanel.lvExpanded = true;
      collapsePanel.collapsePanelShow = true;
      collapsePanel.animationState = 'show';
      if (this.lvAfterExpanded) {
        this.lvAfterExpanded(collapsePanel);
      }
      this.cdr.markForCheck();
    };

    if (!this.lvBeforeExpanded) {
      fn();
    } else {
      const call = this.lvBeforeExpanded(collapsePanel);
      doFnResult(call, (result): void => {
        if (result) {
          fn();
        } else {
          this.cdr.markForCheck();
        }
      });
    }
  }

  collapseStateSubscribe(collapse: CollapsePanelComponent, index: number): void {
    const collapseSub$ = this.subScribeMap.get(collapse) || { expand$: null, viewChange$: null };
    if (!collapseSub$.expand$) {
      const expand$ = collapse.expand$.subscribe((expand): void => {
        if (collapse.lvDisabled || collapse.lvLoading) {
          return;
        }
        if (expand) {
          this.showPanel(index, collapse);
        } else {
          this.hidePanel(collapse);
        }
      });
      this.subScribeMap.set(collapse, Object.assign(collapseSub$, { expand$ }));
    }
    if (!collapseSub$.viewChange$) {
      const viewChange$ = collapse.viewChange$.subscribe((): void => {
        this.cdr.markForCheck();
      });
      this.subScribeMap.set(collapse, Object.assign(collapseSub$, { viewChange$ }));
    }

    // Initial state
    collapse.expand$.next(collapse.lvExpanded);
  }

  ngAfterContentInit(): void {
    this.collapsePanels.forEach((cp, index): void => {
      this.collapseStateSubscribe(cp, index);
    });

    // When collapse panels changed, refresh the view manually
    this.collapsePanels.changes.subscribe((): void => {
      // 展开状态的集合，解决交换时 angular animation remove listener map
      const shownCollapsePanel: Set<CollapsePanelComponent> = new Set();
      this.collapsePanels.forEach((cp, index): void => {
        if (cp.collapsePanelShow) {
          shownCollapsePanel.add(cp);
        }
        this.collapseStateSubscribe(cp, index);
      });

      // 更新节点重新绑定动效listener，解决交换时 angular animation remove listener map
      shownCollapsePanel.forEach((item): void => {
        item.collapsePanelShow = false;
      });
      this.cdr.detectChanges();
      shownCollapsePanel.forEach((item): void => {
        item.collapsePanelShow = true;
      });
      shownCollapsePanel.clear();
      this.cdr.markForCheck();
    });
  }
}
